from xml.dom.minidom import Document
import math
import re
import uuid

# USEFUL CONSTANTS
class Const:
    OBS_CONFIDENCE      = 0.8   # percent
    
    DIST_EPS            = 0.01
    DIST_STATIONARY_EPS = 0.03
    DIST_WINDOW         = 5     # frames
    
    HEADING_NEAR_THRES  = 30    # degrees
    TIME_NEAR_THRES     = 120   # frames
    
    POS_AROUND_THRES    = 2.50  # meters
    POS_NEAR_THRES      = 1.00  # meters
    POS_STABLE_THRES    = 0.25  # meters
    
    SHORT_INT_THRES     = 60    # frames
    MEDIMUM_INT_THRES   = 120   # frames
    
    BIG_GROUP           = 4     # people
    
    MV_SLIGHTLY_FAST    = 6.5
    MV_WALK_FAST        = 8
    MV_RUNNING          = 10
    
    NOISE_SEGMENT       = 5     # frames
    
class NodeNames:
    APPROACH_JOIN       = "approach-join"
    APPROACH_JOIN_GRP   = "approach-join-group"
    BEING_AROUND        = "around"
    BINDING             = "tracks-binding"
    BURY_OBJ            = "burying-obj"
    CIV_CHCKPNT_PATTERN = "civilian-checkpoint-pattern"
    CIVILIAN            = "civilian"
    CIVILIAN_DETECT     = "civilian-detector"
    CROUCH_DETECT       = "crouching-detector"
    DDTS                = "distance-decreasing-to-stationary"
    DDTS_GRP            = "distance-decreasing-to-stationary-group"
    DIFS                = "distance-increasing-from-stationary"
    DIFS_GRP            = "distance-increasing-from-stationary-group"
    DD_LOC              = "distance-decreasing-to-loc"
    DI_LOC              = "distance-increasing-from-loc"
    EXCHANGE_DROP       = "exchanging-dropoff"
    EXCHANGE_INT        = "exchanging-interaction"
    FLEE                = "flee-from"
    GROUP               = "group"
    GROUP_OBS           = "group-observable"
    GROUP_BIG           = "group-big"
    GROUP_BIG_OBS       = "group-big-observable"
    GUARDING_GRP        = "guarding-group"
    GUARDING_LOC        = "guarding-location"
    HOLDING_OBJ         = "holding-obj"
    HOSTILE_INT         = "hostile-interaction"
    INSPECTING          = "inspecting/searching"
    INSPECTION          = "inspection"
    JOINT               = "joint"
    LEAVE_MOVE_AWAY     = "leave-move-away"
    LEAVE_MOVE_AWAY_GRP = "leave-move-away-group"
    LEAVE_PKG           = "leave-with-package"
    LOITERING           = "loitering"
    LOOKAT              = "look-at"
    MIN_MED_INTERVAL    = "min-medium-interval"
    MAX_MED_INTERVAL    = "max-medium-interval"
    MILITARY            = "military"
    MILITARY_DETECT     = "military-detector"
    MIN_LOITERING_TIME  = "min-loitering-time"
    MIN_TIME            = "min-time"
    MISASSOCATION       = "misassociation"
    MVF                 = "moving-fast"
    OBSERVING_OBJ       = "observing-obj"
    OBSERVING_PERSON    = "observing-person"
    OP_EDGES_ALL        = "opposite-edges-all"
    OP_EDGES_LR         = "opposite-edges-leftright"
    OP_EDGES_RL         = "opposite-edges-rightleft"
    OP_EDGES_LR_ALL     = "opposite-edges-leftright-all"
    OP_EDGES_RL_ALL     = "opposite-edges-rightleft-all"
    PASSBY              = "pass-by"
    PASSBY1             = "pass-by-1"
    PASSBY2             = "pass-by-2"
    PASSBY3             = "pass-by-3"
    PASSBY4             = "pass-by-4"
    RUN_AT              = "run-at"
    MIN_SHORT_INTERVAL  = "min-short-interval"
    MAX_SHORT_INTERVAL  = "max-short-interval"
    STAY_NEAR_LOC       = "staying-put-near-loc"
    STAY_PUT            = "staying-put"
    STAY_PUT_GRP        = "staying-put-group"
    WAITING             = "waiting"
    WAITING_PKG         = "waiting-with-package"
    WALKED              = "walked"
    WALKING_GRP         = "walking-group"
    WALKING             = "walking"
    WALK_ACR_GRP        = "walk-across-group"
    WALK_ACR_RL         = "walk-across-rightleft"
    WALK_ACR_LR         = "walk-across-leftright"
    
    DIG_BEHAVIOR        = "digging-behavior"
    GUARDING_BEHAVIOR   = "guarding-behavior"
    STAY_BEHAVIOR       = "staying-put-behavior"
    INSPECTING_BEHAVIOR = "inspecting-behavior"
    
    CARRY_GES           = "carrying-gesture"
    DIG_GES             = "digging-gesture"
    RAISE_HAND_GES      = "raise-hand-gesture"
    STAND_GES           = "standing-gesture"
    WALK_GES            = "walking-gesture"
    
    POS_NEAR            = "position-near"
    POS_NEAR_LOC        = "position-near-location"
    POS_AROUND_LOC      = "position-around-location"
    POS_STABLE          = "position-stable"

# USEFUL FUNCTIONS

def track_start(track):
    """
    Find the first frame that the track existed.
    """
    for t in range(len(track)):
        if track[t][2] > 0.0:
            start = t
            break
    assert not (start is None)
    return start


def track_end(track):
    """
    Find the last frame that the track existed.
    """
    for t in range(len(track)-1, -1, -1):
        if track[t][2] > 0.0:
            end = t
            break
    return end
    

def ave_track_location(track, interval):
    """
    Returns the average location (single point) of the given track over
    the interval.
    """
    #x1, z1, v = zip(*track[interval[0]:interval[1]])
    x = [p[0] for p in track[interval[0]:interval[1]+1] if p[2] > 0.0]
    z = [p[1] for p in track[interval[0]:interval[1]+1] if p[2] > 0.0]
    assert len(x) > 0 and len(x) == len(z), 'Something is wrong with the track!'

    return ( float(sum(x)) / len(x), float(sum(z)) / len(z) )


def ave_group_location(stcm, grp, interval):
    """
    Returns the average location (single point) of all tracks in the group over
    the interval.
    """
    x = []; z = [];
    for i in grp:
        l = ave_track_location(stcm.actors[i].track, interval)
        x.append(l[0])
        z.append(l[1])
    assert len(x) > 0 and len(x) == len(z) and len(x) == len(grp), \
        'Something is wrong with the tracks!'

    return ( sum(x, 0.0) / len(x), sum(z, 0.0) / len(z) )


def group_ptime2ptime(stcm, i, pti, j):
    """
    Returns ptj, an index into the personal timeline of j when i and j are together.
    The returned time should index into the same global time as pti (personal
    time index of i when j & i are together).
    """
    gt = stcm.ptime2glist[i][pti]
    ptj = stcm.glist2ptime[gt][j]
    assert (stcm.ptime2gtime[i][pti] == stcm.ptime2gtime[j][ptj]), 'Wrong ptj!'
    
    return ptj

def euclidean_distance(t1, t2):
    """
    Returns distance between two (tuple) coordinates 
    """
    return math.sqrt( math.pow(t1[0]-t2[0], 2) + math.pow(t1[1]-t2[1], 2) )

def angle2D(v1, v2):
    """
    Returns the angle (in degrees) between two 2D vectors
    """
    return math.degrees( math.atan2(v1[0]*v2[1]-v2[0]*v1[1], v1[0]*v2[0]+v1[1]*v2[1]) )

def factorial(n):
    """
    Factorial.
    """
    return math.factorial(n)

def nCr(n, r):
    """
    n choose r combination.
    """
    return factorial(n) / (factorial(r) * factorial(n-r))

def intlength(interval):
    """
    Returns the length of the interval.
    """
    return interval[1]+1 - interval[0]

def valid_segment(stcm, i, pti):
    """
    Returns true if the time index pti for person i exists AND
    is non-trivial
    """
    return 0 <= pti and pti < len(stcm.timelines[i]) and \
        intlength(stcm.timelines[i][pti][1]) >= Const.NOISE_SEGMENT

def bind(list1, list2):
    """
    Find all possible bindings that map all elements in list1 to elements in list2,
    where each binding maps every element in list1 with a unique element in list2.
    
    Examples:
        bind([1,2],[3,4]) => [[(1, 3), (2, 4)], [(1, 4), (2, 3)]]
    
    Precondition(s):
        - list2 MUST have at least as many elements as list1.
    """
    assert(len(list1) <= len(list2))
    bindings = []
    if len(list1) == 1 or len(list2) == 1:
        bindings = [[(x,y)] for x in list1 for y in list2]
    elif len(list1) > 1 and len(list2) > 1:
        x = list1[0]
        for j in range(len(list2)):
            y = list2[j]
            for rest in bind(list1[1:], list2[:j]+list2[j+1:]):
                rest.insert(0, (x,y))
                bindings.append(rest)
    return bindings

def get_file_ID(filename):
    """
    Returns the integer ID of the file, given that the file has a format that fits the
    pattern: <non-int-prefix>ID<non-int-postfix>
    Example: 'person_1.txt'  => 1
             'person100'     => 100
             '10.txt'        => 10
             '12-1432.txt'   => incompatible
    """
    return int(re.sub('[\D]+', '', filename))
    
def sort_by_ID(listing):
    """
    Sort a list of files intelligently by ID.
    Example:
        ['person_1', 'person10', 'person2']
            => ['person_1', 'person2', 'person10']
    """
    filtered = [l for l in listing if (not (l.startswith('.')) and re.search("\d", l))]
    
    if len(filtered) == 0:
        return []
    
    ids = [get_file_ID(l) for l in filtered]
    
    [ids, sortedlist] = map(list, zip(*sorted(zip(ids, filtered))))
    
    return sortedlist

def find_singleton_node(graph, **kwargs):
    """
    Find node in the graph with the parameters given by kwargs.
    Note: We assert that only one such node exists!
    """
    nodes = graph.which(**kwargs)
    assert len(nodes) == 1, 'Expected 1 node. Received ' + str(len(nodes))
    return nodes[0]

def toXML(desc_results):
    doc = Document()
    
    segmentList = doc.createElement('segmentList')
    doc.appendChild(segmentList)
    
    prevTime = None
    for desc in desc_results:
        if (prevTime is None) or (desc[0] is None) or (prevTime[0] != desc[0][0]) or (prevTime[1] != desc[0][1]):
            segment = doc.createElement('segment')
            segmentList.appendChild(segment)
            
            segment.setAttribute('id', str(uuid.uuid1()))
            if not (desc[0] is None):
                segment.setAttribute('startFrame', str(desc[0][0]))
                segment.setAttribute('endFrame', str(desc[0][1]))
            
            labelList = doc.createElement('labelList')
            segment.appendChild(labelList)
        
        label = doc.createElement('label')
        label.setAttribute('labelType', 'description')
        label.setAttribute('labelValue', str(desc[1]))
        labelList.appendChild(label)
        
        prevTime = desc[0]
    
    return doc.toprettyxml(indent="    ")

